package cn.jimjim.framework.code.generator.common.dao.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.jimjim.framework.common.criteria.AbstractCriteria;
import cn.jimjim.framework.common.dao.BaseDao;
import cn.jimjim.framework.common.entity.Page;
import cn.jimjim.framework.common.model.BaseModel;
import org.jooq.*;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@SuppressWarnings("unchecked")
public abstract class AbstractBaseDao<T extends BaseModel, R extends UpdatableRecord<R>> implements BaseDao<T> {

    /**
     * 获取JOOQ对应的Table
     * @return org.jooq.Table
     */
    public abstract Table<R> getTable();

    @Autowired
    protected DSLContext dsl;

    private Class<T> getModelClass() {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        return (Class<T>) pt.getActualTypeArguments()[0];
    }

    @Override
    public T getById(Serializable id) {
        TableField tableField = getTable().getPrimaryKey().getFields().get(0);
        return dsl.selectFrom(getTable()).where(getTable().field(tableField).eq(id)).fetchOneInto(getModelClass());
    }

    @Override
    public T getByCriteria(AbstractCriteria<T> criteria) {
        return dsl.selectFrom(getTable()).where(criteria.getCondition()).fetchOneInto(getModelClass());
    }

    @Override
    public List<T> list() {
        return dsl.selectFrom(getTable()).fetchInto(getModelClass());
    }

    @Override
    public List<T> listByCriteria(AbstractCriteria<T> criteria) {
        return dsl.selectFrom(getTable()).where(criteria.getCondition()).fetchInto(getModelClass());
    }

    @Override
    public Page<T> pageByCriteria(AbstractCriteria<T> criteria) {
        int pageSize = criteria.getPageSize();
        int pageNum = criteria.getPageNum();

        Page<T> page = new Page<>(pageSize, pageNum);

        int totalSize = dsl.selectCount().from(getTable()).where(criteria.getCondition()).fetchOneInto(int.class);
        page.setTotalSize(totalSize);
        int totalPage = (totalSize + page.getPageSize() -1) / page.getPageSize();
        page.setTotalPage(totalPage);
        page.setHasNextPage(pageNum != totalPage);

        List<T> data;
        if (totalSize != 0) {
            int offset = pageNum * pageSize;
            data = dsl.selectFrom(getTable()).where(criteria.getCondition()).limit(offset).fetchInto(getModelClass());
        } else {
            data = new ArrayList<>();
        }
        page.setData(data);

        return page;
    }

    @Override
    public void insert(T model) {
        List<Field> fieldList = new ArrayList<>();
        List<Object> valueList = new ArrayList<>();

        Map<String, Object> valueMap = BeanUtil.beanToMap(model);

        for (Field field : getTable().fields()) {
            String fieldName = StrUtil.toCamelCase(field.getName());
            if (!valueMap.containsKey(fieldName)) {
                continue;
            }
            if (null == valueMap.get(fieldName)) {
                continue;
            }
            fieldList.add(field);
            valueList.add(valueMap.get(fieldName));
        }

        dsl.insertInto(getTable())
                .columns(fieldList.toArray(new Field[0]))
                .values(valueList).execute();
    }

    @Override
    public void batchInsert(List<T> models) {
        dsl.batchInsert(models.stream().map(this::getRecord).collect(Collectors.toList())).execute();
    }

    private R getRecord(BaseModel model) {
        return dsl.newRecord(getTable(), model);
    }

    @Override
    public void update(T model) {
        Map<String, Object> valueMap = BeanUtil.beanToMap(model);
        UpdateQuery updateQuery = dsl.updateQuery(getTable());
        addValue(valueMap, updateQuery, false);
        updateQuery.execute();
    }

    @Override
    public void updateForSelective(T model) {
        Map<String, Object> valueMap = BeanUtil.beanToMap(model);
        UpdateQuery updateQuery = dsl.updateQuery(getTable());
        addValue(valueMap, updateQuery, true);
        updateQuery.execute();
    }

    @Override
    public void batchUpdate(List<T> models) {
        dsl.batchUpdate(models.stream().map(this::getRecord).collect(Collectors.toList())).execute();
    }

    private void addValue(Map<String, Object> valueMap, UpdateQuery updateQuery, Boolean isSelective) {
        List<String> primaryKeys = getTable().getPrimaryKey().getFields().stream().map(Field::getName).collect(Collectors.toList());
        for (Field field : getTable().fields()) {
            if (!valueMap.containsKey(field.getName())) {
                continue;
            }
            if (primaryKeys.contains(field.getName())) {
                updateQuery.addConditions(field.eq(valueMap.get(field.getName())));
                continue;
            }
            if (isSelective) {
                if (valueMap.get(field.getName()) == null) {
                    continue;
                }
                updateQuery.addValue(field, valueMap.get(field.getName()));
            } else {
                if (valueMap.get(field.getName()) != null) {
                    updateQuery.addValue(field, valueMap.get(field.getName()));
                } else {
                    updateQuery.addValue(field, (Object) null);
                }
            }
            //乐观锁处理
            if ("version".equals(field.getName())) {
                if (valueMap.get(field.getName()) != null) {
                    Integer currentValue = (Integer) valueMap.get(field.getName());
                    updateQuery.addValue(field, currentValue + 1);
                    updateQuery.addConditions(field.eq(currentValue));
                }
            }
        }
    }

    @Override
    public void softDeleteById(Long id) {
        softDeleteByIds(CollectionUtil.newHashSet(id));
    }

    @Override
    public void softDeleteByIds(Set<Long> ids) {
        Field isDeleted = getTable().field("is_deleted");
        if (isDeleted == null) {
            //TODO: 抛错
        }

        UpdateQuery updateQuery = dsl.updateQuery(getTable());
        updateQuery.addValue(isDeleted, true);
        updateQuery.addConditions(getIdField().in(ids));
        updateQuery.execute();
    }

    @Override
    public void hardDeleteById(Long id) {
        hardDeleteByIds(CollectionUtil.newHashSet(id));
    }

    @Override
    public void hardDeleteByIds(Set<Long> ids) {
        DeleteQuery deleteQuery = dsl.deleteQuery(getTable());
        deleteQuery.addConditions(getIdField().in(ids));
        deleteQuery.execute();
    }

    private Field getIdField() {
        return getTable().getPrimaryKey().getFields().stream().filter(field -> "id".equals(field.getName())).findFirst().get();
    }
}
